﻿using System;
using System.Collections;
using System.Collections.Generic;
using MathService.DSP.Signals.Interpolators;

namespace MathService.DSP.Signals
{
    /// <summary>Цифровой сигнал</summary>
    [Serializable]
    public partial class DigitalSignal : ICloneable, IEnumerable<double>
    {
        /* - Поля ------------------------------------------------------------------------------------- */

        #region Поля

        /// <summary>Интерполятор промежуточных значений сигнала</summary>
        private Interpolator f_Interpolator = new Interpolator();

        /// <summary>Значение шага дискретизации сигнала</summary>
        private double f_dx;

        /// <summary>Временное смещение</summary>
        private double f_x0;

        /// <summary>Массив значений сигнала - отсчёты</summary>
        protected double[] f_Samples = { };

        /// <summary>Название сигнала</summary>
        private string f_Name = "";

        private Interval f_Interval;

        #endregion

        /* - Свойства --------------------------------------------------------------------------------- */

        #region Свойства

        /// <summary>Название сигнала</summary>
        public string Name { get { return f_Name; } set { f_Name = value; } }

        /// <summary>Шаг дискретизации сигнала по времени</summary>
        public double dx { get { return f_dx; } set { f_dx = value; f_Interval.Max = Length; } }

        /// <summary>Интерполятор значений сигнала в промежуточные моменты времени</summary>
        public Interpolator ValueInterpolator { get { return f_Interpolator; } set { f_Interpolator = value; } }

        /// <summary>Число отсчётов сигнала</summary>
        public int SamplesCount { get { return f_Samples.Length; } }

        /// <summary>Длительность сигнала</summary>
        public double Length { get { return SamplesCount * f_dx; } }

        /// <summary>Временное смещение</summary>
        public double x0 { get { return f_x0; } set { f_x0 = value; } }

        public double x1 { get { return x0 + Length; } }

        /// <summary>Значение сигнала в момент времени t</summary>
        /// <param name="x">Момент времени</param>
        /// <returns>Значение сигнала в выбранный момент времени</returns>
        public virtual double this[double x]
        {
            get
            {
                var lv_X = (x - x0) / dx;
                return (lv_X >= 0 && lv_X <= SamplesCount) ?
                    ValueInterpolator[lv_X, f_Samples] : 0;
            }
            set
            {
                var lv_X = (x - x0) / dx;
                if(lv_X >= 0 && lv_X < SamplesCount)
                    ValueInterpolator[lv_X, f_Samples] = value;
            }
        }

        /// <summary>Отсчёт сигнала</summary>
        /// <param name="n">Номер отсчёта сигнала</param>
        /// <returns>Отсчёт сигнала с номером n</returns>
        /// <remarks>Если номер отсчёта выходит за пределы, генерируется исключение</remarks>
        /// <exception cref="IndexOutOfRangeException">При выходе за пределы массива значений</exception>
        public virtual double this[int n] { get { return f_Samples[n]; } set { f_Samples[n] = value; } }

        /// <summary>Мощность сигнала</summary>
        /// <remarks>Интеграл квадрата сигнала</remarks>
        public double Power
        {
            get
            {
                var lv_Power = .0;
                var lv_dx = f_dx;

                //f_Samples.Foreach(s => lv_Power += s*s*lv_dx);
                //Array.ForEach(f_Samples, V => lv_Power += V * V * lv_dx);
                var n_Length = f_Samples.Length;
                for(var n = 0; n < n_Length; n++)
                {
                    var lv_Sample = this[n];
                    lv_Power += lv_Sample * lv_Sample * lv_dx;
                }
                return lv_Power;
            }
        }

        public double Integral
        {
            get
            {
                var lv_N = f_Samples.Length;
                var lv_Result = .0;
                var lv_dx = f_dx;
                //Array.ForEach(f_Samples, delegate(double Value) { Result += Value * lv_dx; });
                for(var n = 0; n < lv_N; n++) lv_Result += this[n] * lv_dx;
                return lv_Result;
            }
        }

        /// <summary>Первое значение сигнала</summary>
        public double Value_First { get { return f_Samples[0]; } }

        /// <summary>Последнее значение сигнала</summary>
        public double Value_Last { get { return f_Samples[f_Samples.Length - 1]; } }

        public Interval Interval { get { return f_Interval; } }

        /// <summary>Получить выборку сигнала</summary>
        public virtual double[] Samples { get { return f_Samples; } set { f_Samples = value; } }

        #endregion

        /* - Конструкторы ----------------------------------------------------------------------------- */

        #region Конструкторы

        protected DigitalSignal() { }

        protected DigitalSignal(double dx) { f_dx = dx; }

        //protected DigitalSignal(double dx, double x0) : this(dx) { f_x0 = x0; }

        /// <summary>Цифровой сигнал</summary>
        /// <param name="dx">Шаг дискретизации сигнала по времени</param>
        /// <param name="Samples"></param>
        public DigitalSignal(double dx, double[] Samples)
            : this(dx)
        {
            f_Samples = new double[Samples.Length];
            Array.Copy(Samples, f_Samples, f_Samples.Length);
            f_Interval = new Interval(x0, x0 + Length);
        }

        /// <summary>Цифровой сигнал</summary>
        /// <param name="dx">Шаг дискретизации сигнала по времени</param>
        /// <param name="x0">Смещение сигнала по времени</param>
        /// <param name="Values"></param>
        public DigitalSignal(double dx, double x0, double[] Values)
            : this(dx, Values)
        {
            f_x0 = x0;
            f_Interval = new Interval(x0, x0 + Length);
        }

        public DigitalSignal(double dx, double x0, double Length, Func<double, double> Function)
        {
            var N = (int)(Length / dx);
            f_dx = dx;
            f_x0 = x0;
            f_Samples = new double[N];

            for(var i = 0; i < N; i++) f_Samples[i] = Function(i * dx + x0);
            f_Interval = new Interval(x0, x0 + Length);
        }

        /// <summary>Цифровой сигнал</summary>
        /// <param name="dx">Шаг дискретизации сигнала по времени</param>
        /// <param name="N">Размер выбрки</param>
        public DigitalSignal(double dx, int N) : this(dx, new double[N]) { }

        /// <summary>Цифровой сигнал</summary>
        /// <param name="dx">Шаг дискретизации сигнала по времени</param>
        /// <param name="x0">Смещение сигнала по времени</param>
        /// <param name="N">Размер выбрки</param>
        public DigitalSignal(double dx, double x0, int N) : this(dx, x0, new double[N]) { }

        /// <summary>Цифровой сигнал</summary>
        /// <param name="dx">Шаг дискретизации по времени</param>
        /// <param name="Length">Длительность сигнала</param>
        public DigitalSignal(double dx, double Length) : this(dx, (int)Math.Round(Length / dx)) { }

        /// <summary>Цифровой сигнал</summary>
        /// <param name="dx">Шаг дискретизации по времени</param>
        /// <param name="x0">Смещение сигнала по времени</param>
        /// <param name="Length">Длительность сигнала</param>
        public DigitalSignal(double dx, double x0, double Length)
            : this(dx, x0, (int)Math.Round(Length / dx)) { }

        /// <summary>Конструктор цифрового сигнала по образцу</summary>
        /// <param name="Signal">Цифровой сигнал - аналог для клонирования</param>
        public DigitalSignal(DigitalSignal Signal)
            : this(Signal.dx, Signal.x0, Signal.Samples.Length)
        {
            Signal.Samples.CopyTo(f_Samples, 0);
            f_Interpolator = (Interpolator)Signal.ValueInterpolator.Clone();
        }

        public DigitalSignal(DigitalSignal s1, DigitalSignal s2)
        {
            f_dx = Math.Min(s1.dx, s2.dx);
            var s1_x0 = s1.x0;
            var s1_x1 = s1_x0 + s1.Length;
            var s2_x0 = s2.x0;
            var s2_x1 = s2_x0 + s2.Length;


            f_x0 = Math.Max(s1_x0, s2_x0);
            var lv_X1 = Math.Min(s1_x1, s2_x1);
            if(lv_X1 <= f_x0)
                throw new ArgumentOutOfRangeException("Сигналы не пересикаются по времени");
            f_Samples = new double[(int)Math.Round((lv_X1 - f_x0) / f_dx)];
            f_Interpolator = new Interpolator();
        }

        #endregion

        /* - Методы ----------------------------------------------------------------------------------- */

        #region Методы

        public int SamplePos(double x)
        {
            return (int)((x - f_x0) / f_dx);
        }

        public double GetArgumentValue(int i)
        {
            return i * f_dx + f_x0;
        }

        public void GetMin(out double Min, out int[] MinPos, out double Max, out int[] MaxPos)
        {
            Min = double.PositiveInfinity;
            Max = double.NegativeInfinity;
            var i_Length = SamplesCount;
            var lv_MinPosList = new List<int>(i_Length);
            var lv_MaxPosList = new List<int>(i_Length);

            for(var i = 0; i < i_Length; i++)
            {
                if(f_Samples[i] < Min)
                {
                    Min = f_Samples[i];
                    lv_MinPosList.Clear();
                }
                if(f_Samples[i] == Min)
                    lv_MinPosList.Add(i);
                if(f_Samples[i] > Max)
                {
                    Max = f_Samples[i];
                    lv_MaxPosList.Clear();
                }
                if(f_Samples[i] == Max)
                    lv_MaxPosList.Add(i);
            }
            MinPos = lv_MinPosList.ToArray();
            MaxPos = lv_MaxPosList.ToArray();
        }

        public void GetMin(out double Min, out double Max)
        {
            var lv_Min = double.PositiveInfinity;
            var lv_Max = double.NegativeInfinity;

            Array.ForEach(f_Samples, delegate(double Value)
            {
                if(Value < lv_Min) lv_Min = Value;
                if(Value > lv_Max) lv_Max = Value;
            });

            Min = lv_Min;
            Max = lv_Max;
        }

        public double GetMin()
        {
            var lv_Min = double.PositiveInfinity;
            Array.ForEach(f_Samples, delegate(double Value) { if(Value < lv_Min) lv_Min = Value; });
            return lv_Min;
        }

        public double GetMax()
        {
            var lv_Max = double.NegativeInfinity;
            Array.ForEach(f_Samples, delegate(double Value) { if(Value > lv_Max) lv_Max = Value; });
            return lv_Max;
        }

        public double GetAverage()
        {
            var lv_Count = SamplesCount;
            var lv_Summ = .0;
            for(var i = 0; i < lv_Count; i++) lv_Summ += f_Samples[i];
            return lv_Summ / lv_Count;
        }

        public Func<double, double> GetFunction()
        {
            return t => this[t];
        }

        public DigitalSignal Rediscret(double Dx)
        {
            return Standart.Functional(Dx, f_x0, Length, GetFunction());
        }

        public DigitalSignal[] Trim(double x)
        {
            if(!f_Interval.Check(x))
                throw new ArgumentOutOfRangeException("Временная отметка вне временного интервала сигнала");

            var lv_Result = new DigitalSignal[2];

            x -= x0;
            var n1 = (int)Math.Round(x / dx);
            var n2 = SamplesCount - n1;

            var lv_Values1 = new double[n1];
            var lv_Values2 = new double[n2];
            Array.Copy(f_Samples, lv_Values1, n1);
            Array.Copy(f_Samples, n1, lv_Values2, 0, n2);

            lv_Result[0] = new DigitalSignal(dx, x0, lv_Values1);
            lv_Result[1] = new DigitalSignal(dx, x + x0, lv_Values2);

            return lv_Result;
        }

        public void AddValue(params double[] Value)
        {
            var lv_Length = SamplesCount;
            Array.Resize(ref f_Samples, lv_Length + Value.Length);
            Array.Copy(Value, 0, f_Samples, lv_Length, Value.Length);
        }

        public DigitalSignal GetDelayedSignal(double Delay)
        {
            return CreateDelayed(this, Delay);
        }

        #endregion

        /* -------------------------------------------------------------------------------------------- */

        public override string ToString()
        {
            return string.Format("Signal(dt:{0}; Length:{1}; Values:{2})", f_dx, Length, SamplesCount);
        }

        public override int GetHashCode()
        {
            var lv_HashCode = dx.GetHashCode() ^ x0.GetHashCode();

            var i_Length = SamplesCount;
            for(var i = 0; i < i_Length; i++)
                lv_HashCode ^= this[i].GetHashCode();

            return lv_HashCode;
        }

        public override bool Equals(object obj)
        {
            var s = obj as DigitalSignal;
            if(s != null)
            {
                if(s.dx != dx || s.x0 != x0 || s.SamplesCount != SamplesCount) return false;
                var i_Length = SamplesCount;
                for(var i = 0; i < i_Length; i++)
                    if(this[i] != s[i]) return false;
                return true;
            }
            return base.Equals(obj);
        }

        /* -------------------------------------------------------------------------------------------- */

        #region ICloneable Members

        public object Clone()
        {
            var lv_Signal = (DigitalSignal)MemberwiseClone();
            lv_Signal.f_Samples = (double[])Samples.Clone();
            lv_Signal.f_Interpolator = (Interpolator)lv_Signal.f_Interpolator.Clone();
            return lv_Signal;
        }

        #endregion

        #region IEnumerable<double> Members

        public IEnumerator<double> GetEnumerator()
        {
            return new List<double>(f_Samples).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        /* -------------------------------------------------------------------------------------------- */
    }
}

